package game.breeze.astar;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;

public class AStar implements game.breeze.Disposable {

	private int rows;
	private int columns;
	private AStarNode[][] elements;
	private AStarNode goal;
	private static final String CLOSED_TOP = "+ - ";
	private static final String OPEN_TOP = "+   ";
	private static final String CLOSED_LEFT = "|   ";
	private static final String CLOSED_LEFT_PATH = "| . ";
	private static final String CLOSED_LEFT_START = "| S ";
	private static final String CLOSED_LEFT_GOAL = "| G ";
	private static final String OPEN_LEFT = "    ";
	private static final String OPEN_LEFT_PATH = "  . ";
	// private static final String OPEN_LEFT_START = "  S ";
	private static final String OPEN_LEFT_GOAL = "  G ";
	private List<AStarNode> opened = new ArrayList<AStarNode>();
	private List<AStarNode> closed = new ArrayList<AStarNode>();
	private List<AStarNode> bestList = new ArrayList<AStarNode>();
	private int[][] grid;

	public AStar(int[][] data) {
		this.grid = data;
		this.rows = data.length;
		this.columns = data[0].length;
		elements = new AStarNode[rows][columns];
		init();
	}

	private void init() {

		createSquares();
		// setStartAndGoal();
		generateAdjacenies();
	}

	public int getRows() {

		return rows;
	}

	public int getColumns() {

		return columns;
	}

	protected void setStartAndGoal() {

		elements[0][0].setStart(true);
		Random random = new Random();
		int goalX = 0, goalY = 0;
		while (goalX == 0 && goalY == 0) {
			goalX = random.nextInt(rows);
			goalY = random.nextInt(columns);
		}
		goal = elements[goalX][goalY];
		goal.setEnd(true);
	}

	private void generateAdjacenies() {

		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				elements[i][j].calculateAdjacencies();
			}
		}
	}

	private void createSquares() {

		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				elements[i][j] = new AStarNode(i, j, this);
			}
		}
	}

	public AStarNode getSquare(int x, int y) {

		return elements[x][y];
	}

	public void setSquare(AStarNode square) {

		elements[square.getRow()][square.getCol()] = square;
	}

	public void draw() {

		System.out.println("Drawing maze");
		drawContents();
		drawBorder();
	}

	private void drawContents() {

		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				AStarNode square = elements[i][j];
				drawTop(square);
			}
			System.out.println("+");

			for (int j = 0; j < columns; j++) {
				AStarNode square = elements[i][j];
				drawLeft(square);
			}
			System.out.println("|");
		}
	}

	private void drawLeft(AStarNode square) {

		int x = square.getRow();
		int y = square.getCol();

		if (y - 1 < 0) {
			if (square.isStart()) {
				System.out.print(CLOSED_LEFT_START);
				return;
			}

			if (square.isEnd()) {
				System.out.print(CLOSED_LEFT_GOAL);
				return;
			}

			if (bestList.contains(square)) {
				System.out.print(CLOSED_LEFT_PATH);
				return;
			}
			System.out.print(CLOSED_LEFT);
			return;
		}

		for (AStarNode neighbor : square.getAdjacencies()) {
			if (neighbor.getRow() == x && neighbor.getCol() == y - 1) {
				if (square.isEnd()) {
					System.out.print(OPEN_LEFT_GOAL);
					return;
				}
				if (bestList.contains(square)) {
					System.out.print(OPEN_LEFT_PATH);
					return;
				}
				System.out.print(OPEN_LEFT);
				return;
			}
		}

		if (square.isEnd()) {
			System.out.print(CLOSED_LEFT_GOAL);
			return;
		}

		if (bestList.contains(square)) {
			System.out.print(CLOSED_LEFT_PATH);
			return;
		}
		System.out.print(CLOSED_LEFT);

	}

	private void drawTop(AStarNode square) {

		int x = square.getRow();
		int y = square.getCol();

		if (x == 0) {
			System.out.print(CLOSED_TOP);
			return;
		}

		for (AStarNode neighbor : square.getAdjacencies()) {
			if (neighbor.getRow() == x - 1 && neighbor.getCol() == y) {
				System.out.print(OPEN_TOP);
				return;
			}
		}

		System.out.print(CLOSED_TOP);
	}

	private void drawBorder() {

		for (int i = 0; i < columns; i++) {
			System.out.print(CLOSED_TOP);
		}
		System.out.println("+");
	}

	public List<AStarNode> astar(int row, int col, int drow, int dcol) {
		bestList.clear();
		opened.clear();
		closed.clear();

		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				elements[i][j].reset();
			}
		}

		if (drow >= rows || dcol >= columns) {
			return bestList;
		}

		elements[row][col].setStart(true);
		goal = elements[drow][dcol];
		goal.setEnd(true);

		// System.out.println("Calculating best path...");
		Set<AStarNode> adjacencies = elements[row][col].getAdjacencies();
		for (AStarNode adjacency : adjacencies) {
			adjacency.setParent(elements[row][col]);
			if (adjacency.isStart() == false) {
				opened.add(adjacency);
			}
		}

		while (opened.size() > 0) {
			AStarNode best = findBestPassThrough();
			opened.remove(best);
			closed.add(best);
			if (best.isEnd()) {
				// System.out.println("Found Goal");
				populateBestList(goal);
				// draw();
				// return;
			} else {
				Set<AStarNode> neighbors = best.getAdjacencies();
				for (AStarNode neighbor : neighbors) {
					if (opened.contains(neighbor)) {
						// AStarNode tmpSquare = new
						// AStarNode(neighbor.getRow(), neighbor.getCol(),
						// this);
						// tmpSquare.setParent(best);
						// if (tmpSquare.getPassThrough(goal) >=
						// neighbor.getPassThrough(goal)) {
						continue;
						// }
					}

					if (closed.contains(neighbor)) {
						// AStarNode tmpSquare = new
						// AStarNode(neighbor.getRow(), neighbor.getCol(),
						// this);
						// tmpSquare.setParent(best);
						// if (tmpSquare.getPassThrough(goal) >=
						// neighbor.getPassThrough(goal)) {
						continue;
						// }
					}

					neighbor.setParent(best);

					opened.remove(neighbor);
					closed.remove(neighbor);
					opened.add(0, neighbor);
				}
			}
		}
		// System.out.println("No Path to goal");
		return bestList;
	}

	private void populateBestList(AStarNode square) {

		bestList.add(square);
		if (square.getParent().isStart() == false) {
			populateBestList(square.getParent());
		}

		return;
	}

	private AStarNode findBestPassThrough() {

		AStarNode best = null;
		for (AStarNode square : opened) {
			if (best == null || square.getPassThrough(goal) < best.getPassThrough(goal)) {
				best = square;
			}
		}

		return best;
	}

	/**
	 * @return the grid
	 */
	public int[][] getGrid() {
		return grid;
	}

	@Override
	public void dispose() {
		goal = null;
		opened.clear();
		closed.clear();
		bestList.clear();
		opened = null;
		closed = null;
		bestList = null;
		grid = null;
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				elements[i][j].dispose();
			}
		}
		elements = null;
	}
}
